package com.test.lucene.index;

import java.io.File;
import java.io.IOException;
import java.util.Collection;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.CorruptIndexException;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.store.LockObtainFailedException;

import com.subshell.lucene.indexaccess.ILuceneIndexAccessor;
import com.test.lucene.LuceneException;
import com.test.lucene.LuceneRetryException;
import com.test.lucene.helper.Constants;

/**
 * Manage the Lucene document collection.
 * 
 * @author Patrick Kimber
 * 
 */
public final class DocumentCollection {

    /** Exception message. */
    private static final String CANNOT_CREATE_REPOSITORY = "Cannot create repository.";

    /** The logger. */
    private final Log logger = LogFactory.getLog(DocumentCollection.class);

    /** The folder for the repository. */
    private File repositoryFolder;

    /**
     * Constructor.
     * 
     * @param folder
     *            The folder containing the Lucene repository.
     */
    public DocumentCollection(final String folder) {
        setRepositoryFolder(folder);
    }

    /**
     * Search the Lucene repository.
     * 
     * @param criteria
     *            Search criteria.
     * @return A collection of search results.
     * @throws LuceneException
     *             If the search fails.
     */
    public Collection search(final String criteria) throws LuceneException {
        return new IndexSearch().search(getIndexAccessor(), criteria);
    }

    /**
     * Close the Lucene repository.
     * 
     * @throws LuceneException
     *             If the repository cannot be closed.
     */
    public void close() throws LuceneException {
        getIndexAccessor().close();
    }

    /**
     * Create the Lucene repository.
     * 
     * @throws LuceneException
     *             If the repository cannot be created.
     */
    public void create() throws LuceneException {
        logger.info("create(" + getRepositoryFolder().getAbsolutePath() + ")");
        checkRepositoryFolder();
        try {
            new IndexWriter(getRepositoryFolder(), new StandardAnalyzer(), true)
                    .close();
        } catch (CorruptIndexException e) {
            throw new LuceneException(CANNOT_CREATE_REPOSITORY, e);
        } catch (LockObtainFailedException e) {
            throw new LuceneException(CANNOT_CREATE_REPOSITORY, e);
        } catch (IOException e) {
            throw new LuceneException(CANNOT_CREATE_REPOSITORY, e);
        }
    }

    /**
     * Delete a document from the Lucene repository.
     * 
     * @param documentNumber
     *            Delete the document with this number.
     * @throws LuceneException
     *             If the document cannot be deleted.
     */
    public void delete(final int documentNumber) throws LuceneException {
        new IndexDelete().delete(getIndexAccessor(), documentNumber);
    }

    /**
     * Check the repository folder to make sure it does not already exist.
     * 
     * @throws LuceneException
     *             If the folder already exists.
     */
    private void checkRepositoryFolder() throws LuceneException {
        if (getRepositoryFolder().isDirectory()) {
            throw new LuceneException("Repository folder already exists ["
                    + getRepositoryFolder().getAbsolutePath() + "]");
        }
    }

    /**
     * Add a document to the Lucene repository.
     * 
     * @param document
     *            Add this document to the Lucene repository.
     * @throws LuceneException
     *             If the repository cannot be updated.
     */
    public void update(final Document document) throws LuceneException {
        logger.info("update(key:" + document.get(Constants.KEY) + ")");
        RetryHandler handler = new RetryHandler(this.getClass().getName());
        Exception thrown;
        do {
            try {
                thrown = null;
                addDocument(document);
            } catch (LuceneRetryException e) {
                thrown = e;
            }
        } while (handler.retry(thrown));
    }

    /**
     * Add a document to the Lucene repository.
     * 
     * @param document
     *            Add this document to the Lucene repository.
     * @throws LuceneException
     *             If the repository cannot be updated.
     */
    public void addDocument(final Document document) throws LuceneException {
        logger.info("addDocument(key:" + document.get(Constants.KEY) + ")");
        ILuceneIndexAccessor indexAccessor = getIndexAccessor();
        IndexWriter writer = null;
        try {
            writer = indexAccessor.getWriter();
            writer.addDocument(document);
        } catch (LockObtainFailedException e) {
            throw new LuceneRetryException(e);
        } catch (IOException e) {
            throw new LuceneException("Cannot get writer.", e);
        } finally {
            indexAccessor.release(writer);
        }
    }

    /**
     * Get the Lucene index accessor object.
     * 
     * @return The Lucene index accessor.
     * @throws LuceneException
     *             If the index accessor cannot be created.
     */
    private ILuceneIndexAccessor getIndexAccessor() throws LuceneException {
        return IndexAccessorController.getInstance(getRepositoryFolder());
    }

    /**
     * @param folder
     *            The folder for the Lucene repository .
     */
    private void setRepositoryFolder(final String folder) {
        repositoryFolder = new File(folder);
    }

    /** @return the repositoryFolder. */
    private File getRepositoryFolder() {
        return repositoryFolder;
    }
}
